﻿using NewsReader.Helpers;
using NewsReader.Log;
using NewsReader.ViewModels;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.ApplicationModel.Core;
using Windows.Networking.Connectivity;
using Windows.UI.Core;

namespace NewsReaderApp.ViewModels
{
    public class FeedViewModel : INotifyPropertyChanged
    {
        private FeedInfo _selectedFeed;
        private readonly ObservableCollection<object> _feeds = new ObservableCollection<object>();
        private readonly DelegateCommand _reloadCommand;
        private readonly DelegateCommand _showFeedSelectorCommand;
        private bool _loadCompleted;

        public event PropertyChangedEventHandler PropertyChanged;
        public FeedViewModel()
        {
            _reloadCommand = new DelegateCommand(o => ReloadFromInternet(), o => ViewModelHelper.IsConnected);
            _showFeedSelectorCommand = new DelegateCommand(o => { }, o => LoadCompleted);
            NetworkInformation.NetworkStatusChanged += o => _reloadCommand.RaiseCanExecuteChanged();
        }

        public async Task Begin(bool clear)
        {
            if (clear)
            {
                await PersistenceHelper.DeleteFeedsFromDBAsync();
                _feeds.Clear();
            }

            if (await LoadFeedsFromDB())
            {
                if (SelectedFeed == null)
                {
                    SelectedFeed = _feeds.OfType<FeedInfo>().FirstOrDefault(f => f.IsLoaded);
                }
            }

            if (ViewModelHelper.IsConnected)
            {
                await LoadFeedsFromTheInternet(await PersistenceHelper.GetUserFeedsAsync());
            }
            else if (SelectedFeed != null)
            {
                if (SelectedFeed.SelectedItem == null)
                {
                    SelectedFeed.SelectedItem = (FeedItem) SelectedFeed.FeedItems.FirstOrDefault();
                }
            }
            this.RaisePropertyChanged(PropertyChanged, "Feeds");
        }

        public void ReloadFromInternet()
        {
            Task.Factory.StartNew(async () => 
            {
                await LoadFeedsFromTheInternet(await PersistenceHelper.GetUserFeedsAsync());
                this.RaisePropertyChanged(PropertyChanged, "Feeds");
            });
        }

        public ICommand ShowFeedSelectorCommand
        {
            get { return _showFeedSelectorCommand;}
        }

        public ICommand ReloadFromInternetCommand
        {
            get { return _reloadCommand; }
        }

        public ICommand OpenInBrowserCommand
        {
            get { return new DelegateCommand(o => OpenSelectedItemInBrowser(), o => SelectedFeed != null && SelectedFeed.SelectedItem != null); }
        }

        private void OpenSelectedItemInBrowser()
        {
            Windows.System.Launcher.LaunchUriAsync(SelectedFeed.SelectedItem.Link).AsTask().ContinueWith(t =>
            {
                if (t.IsFaulted)
                {
                    Logger.Log.ErrorFormat("LaunchUriAsync() failed. Exception {0}", t.Exception);
                }
            });
        }


        public async Task LoadFeedsFromTheInternet(IEnumerable<FeedConfig> feeds)
        {
            LoadCompleted = false;
            
            List<Task> tasks = await ScheduleFeedLoading(feeds);
            await WaitForAllTasksAsync(tasks);

            SettingsManager.Instance.ParserEngineChanged = false;

            if (_feeds.Count > 0 && SelectedFeed == null)
            {
                SelectedFeed = _feeds.OfType<FeedInfo>().FirstOrDefault(f => f.IsLoaded);
            }
            
            await WaitForAllTextToLoadAndSave(_feeds.Cast<FeedInfo>());
            
            LoadCompleted = true;
        }

        private async Task<List<Task>> ScheduleFeedLoading(IEnumerable<FeedConfig> feeds)
        {
            List<Task> tasks = new List<Task>();
            CoreDispatcher dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
            foreach (var fc in feeds)
            {
                try
                {
                    var fi = (FeedInfo)_feeds.FirstOrDefault(f => ((FeedInfo)f).Uri.AbsoluteUri == fc.Uri);
                    if (fi == null)
                    {
                        fi = new FeedInfo(fc.Title, fc.Uri);
                        await dispatcher.RunAsync(CoreDispatcherPriority.Normal,
                                      new Windows.UI.Core.DispatchedHandler(() =>
                                      {
                                          _feeds.Add(fi);
                                      }));
                    }

                    tasks.Add(fi.BeginLoad());
                }
                catch (Exception ex)
                {
                    Logger.Log.ErrorFormat("Failed to load FeedInfo. Exception {0}", ex);
                }
            }
            return tasks;
        }

        private static Task WaitForAllTasksAsync(List<Task> tasks)
        {
            return Task.Factory.StartNew(() =>
            {
                try
                {
                    Task.WaitAll(tasks.ToArray());
                }
                catch (AggregateException aex)
                {
                    foreach (Exception ex in aex.InnerExceptions)
                    {
                        Logger.Log.ErrorFormat("Error from loading task {0}", ex);
                    }
                }
                catch (Exception ex)
                {
                    Logger.Log.ErrorFormat("Exception loading feeds {0}", ex);
                }
            });
        }

        private async Task WaitForAllTextToLoadAndSave(IEnumerable<FeedInfo> feeds)
        {
            try
            {
                var allTextLoadTasks = _feeds.Cast<FeedInfo>()
                 .SelectMany(fi => fi.FeedItems).Cast<FeedItem>()
                 .Select(info => info.GetLoadFullTextTask());
                
                await Task.WhenAll(allTextLoadTasks.ToArray());
                await PersistenceHelper.SaveFeedsToDBAsync(feeds);
            }
            catch (Exception ex)
            {
                Logger.Log.ErrorFormat("Failed to save feeds.  Exception {0}", ex);
            }
        }

        private class FeedInfoObjectEqualityComparer : IEqualityComparer<object>
        {
            public new bool Equals(object x, object y)
            {
                if (x == null && y == null)
                {
                    return true;
                }
                else if (x == null || y == null)
                {
                    return false;
                }

                if (x is FeedInfo && y is FeedInfo)
                {
                    return ((FeedInfo)x).Uri == ((FeedInfo)y).Uri;
                }
                return false;
            }

            public int GetHashCode(object obj)
            {
                return ((FeedInfo)obj).Uri.GetHashCode();
            }
        }

        public async Task<bool> LoadFeedsFromDB()
        {
            try
            {
                var feeds = await PersistenceHelper.GetFeedsFromDBAsync();
                foreach (var feed in feeds.Except(_feeds, new FeedInfoObjectEqualityComparer()))
                {
                    _feeds.Add(feed);
                }
                return _feeds.Count != 0;
            }
            catch (Exception ex)
            {
                Logger.Log.ErrorFormat("Failed to get feeds from DB due to an error. {0}", ex);
                return false;
            }
        }

        public String Title
        {
            get { return _selectedFeed == null ? null : _selectedFeed.Title; }
        }

        public String Description
        {
            get { return _selectedFeed == null ? null : _selectedFeed.Description; }
        }

        public bool LoadCompleted
        {
            get { return _loadCompleted; }
            set
            {
                if (this.ViewModelPropertyChanged("LoadCompleted", value, ref _loadCompleted, PropertyChanged))
                {
                    _showFeedSelectorCommand.RaiseCanExecuteChanged();
                }
            }
        }

        public FeedInfo SelectedFeed
        {
            get { return _selectedFeed; }
            set
            {
                if (this.ViewModelPropertyChanged("SelectedFeed", value, ref _selectedFeed, PropertyChanged))
                {
                    this.RaisePropertyChanged(PropertyChanged, "FeedItems");

                    if (_selectedFeed != null)
                    {
                        if (_selectedFeed.SelectedItem == null)
                        {
                            _selectedFeed.SelectedItem = _selectedFeed.FeedItems.FirstOrDefault() as FeedItem;
                        }
                    }
                }
            }
        }

        public IEnumerable<object> FeedItems
        {
            get { return _selectedFeed == null ? null : _selectedFeed.FeedItems; }
        }

        public ObservableCollection<object> Feeds
        {
            get { return _feeds; }
        }
    }
}